// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/audio/mac/audio_low_latency_input_mac.h"
#include <CoreServices/CoreServices.h>
#include <mach/mach.h>
#include <string>

#include "base/bind.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/base/audio_bus.h"
#include "media/base/data_buffer.h"

namespace media {

// Number of blocks of buffers used in the |fifo_|.
const int kNumberOfBlocksBufferInFifo = 2;

// Max length of sequence of TooManyFramesToProcessError errors.
// The stream will be stopped as soon as this time limit is passed.
const int kMaxErrorTimeoutInSeconds = 1;

// A repeating timer is created and started in Start() and it triggers calls
// to CheckIfInputStreamIsAlive() where we do periodic checks to see if the
// input data callback sequence is active or not. If the stream seems dead,
// up to |kMaxNumberOfRestartAttempts| restart attempts tries to bring the
// stream back to life.
const int kCheckInputIsAliveTimeInSeconds = 5;

// Number of restart indications required to actually trigger a restart
// attempt.
const int kNumberOfIndicationsToTriggerRestart = 1;

// Max number of times we try to restart a stream when it has been categorized
// as dead. Note that we can do many restarts during an audio session and this
// limitation is per detected problem. Once a restart has succeeded, a new
// sequence of |kMaxNumberOfRestartAttempts| number of restart attempts can be
// done.
const int kMaxNumberOfRestartAttempts = 1;

// A one-shot timer is created and started in Start() and it triggers
// CheckInputStartupSuccess() after this amount of time. UMA stats marked
// Media.Audio.InputStartupSuccessMac is then updated where true is added
// if input callbacks have started, and false otherwise. Note that the value
// is larger than |kCheckInputIsAliveTimeInSeconds| to ensure that at least one
// restart attempt can be done before storing the result.
const int kInputCallbackStartTimeoutInSeconds = kCheckInputIsAliveTimeInSeconds + 3;

// Returns true if the format flags in |format_flags| has the "non-interleaved"
// flag (kAudioFormatFlagIsNonInterleaved) cleared (set to 0).
static bool FormatIsInterleaved(UInt32 format_flags)
{
    return !(format_flags & kAudioFormatFlagIsNonInterleaved);
}

// Converts the 32-bit non-terminated 4 byte string into an std::string.
// Example: code=1735354734 <=> 'goin' <=> kAudioDevicePropertyDeviceIsRunning.
static std::string FourCharFormatCodeToString(UInt32 code)
{
    char code_string[5];
    // Converts a 32-bit integer from the host’s native byte order to big-endian.
    UInt32 code_id = CFSwapInt32HostToBig(code);
    bcopy(&code_id, code_string, 4);
    code_string[4] = '\0';
    return std::string(code_string);
}

static std::ostream& operator<<(std::ostream& os,
    const AudioStreamBasicDescription& format)
{
    std::string format_string = FourCharFormatCodeToString(format.mFormatID);
    os << "sample rate       : " << format.mSampleRate << std::endl
       << "format ID         : " << format_string << std::endl
       << "format flags      : " << format.mFormatFlags << std::endl
       << "bytes per packet  : " << format.mBytesPerPacket << std::endl
       << "frames per packet : " << format.mFramesPerPacket << std::endl
       << "bytes per frame   : " << format.mBytesPerFrame << std::endl
       << "channels per frame: " << format.mChannelsPerFrame << std::endl
       << "bits per channel  : " << format.mBitsPerChannel << std::endl
       << "reserved          : " << format.mReserved << std::endl
       << "interleaved       : "
       << (FormatIsInterleaved(format.mFormatFlags) ? "yes" : "no");
    return os;
}

// Property address to monitor device changes. Use wildcards to match any and
// all values for their associated type. Filtering for device-specific
// notifications will take place in the callback.
const AudioObjectPropertyAddress
    AUAudioInputStream::kDeviceChangePropertyAddress
    = {
          kAudioObjectPropertySelectorWildcard, kAudioObjectPropertyScopeWildcard,
          kAudioObjectPropertyElementWildcard
      };

// Maps internal enumerator values (e.g. kAudioDevicePropertyDeviceHasChanged)
// into local values that are suitable for UMA stats.
// See AudioObjectPropertySelector in CoreAudio/AudioHardware.h for details.
// TODO(henrika): ensure that the "other" bucket contains as few counts as
// possible by adding more valid enumerators below.
enum AudioDevicePropertyResult {
    PROPERTY_OTHER = 0, // Use for all non-supported property changes
    PROPERTY_DEVICE_HAS_CHANGED = 1,
    PROPERTY_IO_STOPPED_ABNORMALLY = 2,
    PROPERTY_HOG_MODE = 3,
    PROPERTY_BUFFER_FRAME_SIZE = 4,
    PROPERTY_BUFFER_FRAME_SIZE_RANGE = 5,
    PROPERTY_STREAM_CONFIGURATION = 6,
    PROPERTY_ACTUAL_SAMPLE_RATE = 7,
    PROPERTY_NOMINAL_SAMPLE_RATE = 8,
    PROPERTY_DEVICE_IS_RUNNING_SOMEWHERE = 9,
    PROPERTY_DEVICE_IS_RUNNING = 10,
    PROPERTY_DEVICE_IS_ALIVE = 11,
    PROPERTY_STREAM_PHYSICAL_FORMAT = 12,
    PROPERTY_JACK_IS_CONNECTED = 13,
    PROPERTY_PROCESSOR_OVERLOAD = 14,
    PROPERTY_DATA_SOURCES = 15,
    PROPERTY_DATA_SOURCE = 16,
    PROPERTY_VOLUME_DECIBELS = 17,
    PROPERTY_VOLUME_SCALAR = 18,
    PROPERTY_MUTE = 19,
    PROPERTY_PLUGIN = 20,
    PROPERTY_USES_VARIABLE_BUFFER_FRAME_SIZES = 21,
    PROPERTY_IO_CYCLE_USAGE = 22,
    PROPERTY_IO_PROC_STREAM_USAGE = 23,
    PROPERTY_CONFIGURATION_APPLICATION = 24,
    PROPERTY_DEVICE_UID = 25,
    PROPERTY_MODE_UID = 26,
    PROPERTY_TRANSPORT_TYPE = 27,
    PROPERTY_RELATED_DEVICES = 28,
    PROPERTY_CLOCK_DOMAIN = 29,
    PROPERTY_DEVICE_CAN_BE_DEFAULT_DEVICE = 30,
    PROPERTY_DEVICE_CAN_BE_DEFAULT_SYSTEM_DEVICE = 31,
    PROPERTY_LATENCY = 32,
    PROPERTY_STREAMS = 33,
    PROPERTY_CONTROL_LIST = 34,
    PROPERTY_SAFETY_OFFSET = 35,
    PROPERTY_AVAILABLE_NOMINAL_SAMPLE_RATES = 36,
    PROPERTY_ICON = 37,
    PROPERTY_IS_HIDDEN = 38,
    PROPERTY_PREFERRED_CHANNELS_FOR_STEREO = 39,
    PROPERTY_PREFERRED_CHANNEL_LAYOUT = 40,
    PROPERTY_VOLUME_RANGE_DECIBELS = 41,
    PROPERTY_VOLUME_SCALAR_TO_DECIBELS = 42,
    PROPERTY_VOLUME_DECIBEL_TO_SCALAR = 43,
    PROPERTY_STEREO_PAN = 44,
    PROPERTY_STEREO_PAN_CHANNELS = 45,
    PROPERTY_SOLO = 46,
    PROPERTY_PHANTOM_POWER = 47,
    PROPERTY_PHASE_INVERT = 48,
    PROPERTY_CLIP_LIGHT = 49,
    PROPERTY_TALKBACK = 50,
    PROPERTY_LISTENBACK = 51,
    PROPERTY_CLOCK_SOURCE = 52,
    PROPERTY_CLOCK_SOURCES = 53,
    PROPERTY_SUB_MUTE = 54,
    PROPERTY_MAX = PROPERTY_SUB_MUTE
};

// Add the provided value in |result| to a UMA histogram.
static void LogDevicePropertyChange(bool startup_failed,
    AudioDevicePropertyResult result)
{
    if (startup_failed) {
        UMA_HISTOGRAM_ENUMERATION(
            "Media.Audio.InputDevicePropertyChangedStartupFailedMac", result,
            PROPERTY_MAX + 1);
    } else {
        UMA_HISTOGRAM_ENUMERATION("Media.Audio.InputDevicePropertyChangedMac",
            result, PROPERTY_MAX + 1);
    }
}

static OSStatus GetInputDeviceStreamFormat(
    AudioUnit audio_unit,
    AudioStreamBasicDescription* format)
{
    DCHECK(audio_unit);
    UInt32 property_size = sizeof(*format);
    // Get the audio stream data format on the input scope of the input element
    // since it is connected to the current input device.
    OSStatus result = AudioUnitGetProperty(audio_unit, kAudioUnitProperty_StreamFormat,
        kAudioUnitScope_Input, 1, format, &property_size);
    DVLOG(1) << "Input device stream format: " << *format;
    return result;
}

// Returns the number of physical processors on the device.
static int NumberOfPhysicalProcessors()
{
    mach_port_t mach_host = mach_host_self();
    host_basic_info hbi = {};
    mach_msg_type_number_t info_count = HOST_BASIC_INFO_COUNT;
    kern_return_t kr = host_info(mach_host, HOST_BASIC_INFO, reinterpret_cast<host_info_t>(&hbi),
        &info_count);
    mach_port_deallocate(mach_task_self(), mach_host);

    int n_physical_cores = 0;
    if (kr != KERN_SUCCESS) {
        n_physical_cores = 1;
        LOG(ERROR) << "Failed to determine number of physical cores, assuming 1";
    } else {
        n_physical_cores = hbi.physical_cpu;
    }
    DCHECK_EQ(HOST_BASIC_INFO_COUNT, info_count);
    return n_physical_cores;
}

// Adds extra system information to Media.AudioXXXMac UMA statistics.
// Only called when it has been detected that audio callbacks does not start
// as expected.
static void AddSystemInfoToUMA(bool is_on_battery, int num_resumes)
{
    // Logs true or false depending on if the machine is on battery power or not.
    UMA_HISTOGRAM_BOOLEAN("Media.Audio.IsOnBatteryPowerMac", is_on_battery);
    // Number of logical processors/cores on the current machine.
    UMA_HISTOGRAM_COUNTS("Media.Audio.LogicalProcessorsMac",
        base::SysInfo::NumberOfProcessors());
    // Number of physical processors/cores on the current machine.
    UMA_HISTOGRAM_COUNTS("Media.Audio.PhysicalProcessorsMac",
        NumberOfPhysicalProcessors());
    // Counts number of times the system has resumed from power suspension.
    UMA_HISTOGRAM_COUNTS_1000("Media.Audio.ResumeEventsMac", num_resumes);
    // System uptime in hours.
    UMA_HISTOGRAM_COUNTS_1000("Media.Audio.UptimeMac",
        base::SysInfo::Uptime().InHours());
    DVLOG(1) << "uptime: " << base::SysInfo::Uptime().InHours();
    DVLOG(1) << "logical processors: " << base::SysInfo::NumberOfProcessors();
    DVLOG(1) << "physical processors: " << NumberOfPhysicalProcessors();
    DVLOG(1) << "battery power: " << is_on_battery;
    DVLOG(1) << "resume events: " << num_resumes;
}

// See "Technical Note TN2091 - Device input using the HAL Output Audio Unit"
// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
// for more details and background regarding this implementation.

AUAudioInputStream::AUAudioInputStream(
    AudioManagerMac* manager,
    const AudioParameters& input_params,
    AudioDeviceID audio_device_id,
    const AudioManager::LogCallback& log_callback)
    : manager_(manager)
    , number_of_frames_(input_params.frames_per_buffer())
    , number_of_frames_provided_(0)
    , io_buffer_frame_size_(0)
    , sink_(nullptr)
    , audio_unit_(0)
    , input_device_id_(audio_device_id)
    , hardware_latency_frames_(0)
    , number_of_channels_in_frame_(0)
    , fifo_(input_params.channels(),
          number_of_frames_,
          kNumberOfBlocksBufferInFifo)
    , input_callback_is_active_(false)
    , start_was_deferred_(false)
    , buffer_size_was_changed_(false)
    , audio_unit_render_has_worked_(false)
    , device_listener_is_active_(false)
    , last_sample_time_(0.0)
    , last_number_of_frames_(0)
    , total_lost_frames_(0)
    , largest_glitch_frames_(0)
    , glitches_detected_(0)
    , number_of_restart_indications_(0)
    , number_of_restart_attempts_(0)
    , total_number_of_restart_attempts_(0)
    , log_callback_(log_callback)
    , weak_factory_(this)
{
    DCHECK(manager_);
    CHECK(!log_callback_.Equals(AudioManager::LogCallback()));

    // Set up the desired (output) format specified by the client.
    format_.mSampleRate = input_params.sample_rate();
    format_.mFormatID = kAudioFormatLinearPCM;
    format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger;
    DCHECK(FormatIsInterleaved(format_.mFormatFlags));
    format_.mBitsPerChannel = input_params.bits_per_sample();
    format_.mChannelsPerFrame = input_params.channels();
    format_.mFramesPerPacket = 1; // uncompressed audio
    format_.mBytesPerPacket = (format_.mBitsPerChannel * input_params.channels()) / 8;
    format_.mBytesPerFrame = format_.mBytesPerPacket;
    format_.mReserved = 0;

    DVLOG(1) << "ctor";
    DVLOG(1) << "device ID: 0x" << std::hex << audio_device_id;
    DVLOG(1) << "buffer size : " << number_of_frames_;
    DVLOG(1) << "channels : " << input_params.channels();
    DVLOG(1) << "desired output format: " << format_;

    // Derive size (in bytes) of the buffers that we will render to.
    UInt32 data_byte_size = number_of_frames_ * format_.mBytesPerFrame;
    DVLOG(1) << "size of data buffer in bytes : " << data_byte_size;

    // Allocate AudioBuffers to be used as storage for the received audio.
    // The AudioBufferList structure works as a placeholder for the
    // AudioBuffer structure, which holds a pointer to the actual data buffer.
    audio_data_buffer_.reset(new uint8_t[data_byte_size]);
    // We ask for noninterleaved audio.
    audio_buffer_list_.mNumberBuffers = 1;

    AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers;
    audio_buffer->mNumberChannels = input_params.channels();
    audio_buffer->mDataByteSize = data_byte_size;
    audio_buffer->mData = audio_data_buffer_.get();
}

AUAudioInputStream::~AUAudioInputStream()
{
    DVLOG(1) << "~dtor";
    DCHECK(!device_listener_is_active_);
    ReportAndResetStats();
}

// Obtain and open the AUHAL AudioOutputUnit for recording.
bool AUAudioInputStream::Open()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DVLOG(1) << "Open";
    DCHECK(!audio_unit_);

    // Verify that we have a valid device. Send appropriate error code to
    // HandleError() to ensure that the error type is added to UMA stats.
    if (input_device_id_ == kAudioObjectUnknown) {
        NOTREACHED() << "Device ID is unknown";
        HandleError(kAudioUnitErr_InvalidElement);
        return false;
    }

    // Start listening for changes in device properties.
    RegisterDeviceChangeListener();

    // The requested sample-rate must match the hardware sample-rate.
    int sample_rate = AudioManagerMac::HardwareSampleRateForDevice(input_device_id_);
    DCHECK_EQ(sample_rate, format_.mSampleRate);

    // Start by obtaining an AudioOuputUnit using an AUHAL component description.

    // Description for the Audio Unit we want to use (AUHAL in this case).
    // The kAudioUnitSubType_HALOutput audio unit interfaces to any audio device.
    // The user specifies which audio device to track. The audio unit can do
    // input from the device as well as output to the device. Bus 0 is used for
    // the output side, bus 1 is used to get audio input from the device.
    AudioComponentDescription desc = {
        kAudioUnitType_Output,
        kAudioUnitSubType_HALOutput,
        kAudioUnitManufacturer_Apple,
        0,
        0
    };

    // Find a component that meets the description in |desc|.
    AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
    DCHECK(comp);
    if (!comp) {
        HandleError(kAudioUnitErr_NoConnection);
        return false;
    }

    // Get access to the service provided by the specified Audio Unit.
    OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
    if (result) {
        HandleError(result);
        return false;
    }

    // Initialize the AUHAL before making any changes or using it. The audio unit
    // will be initialized once more as last operation in this method but that is
    // intentional. This approach is based on a comment in the CAPlayThrough
    // example from Apple, which states that "AUHAL needs to be initialized
    // *before* anything is done to it".
    // TODO(henrika): remove this extra call if we are unable to see any positive
    // effects of it in our UMA stats.
    result = AudioUnitInitialize(audio_unit_);
    if (result != noErr) {
        HandleError(result);
        return false;
    }

    // Enable IO on the input scope of the Audio Unit.
    // Note that, these changes must be done *before* setting the AUHAL's
    // current device.

    // After creating the AUHAL object, we must enable IO on the input scope
    // of the Audio Unit to obtain the device input. Input must be explicitly
    // enabled with the kAudioOutputUnitProperty_EnableIO property on Element 1
    // of the AUHAL. Because the AUHAL can be used for both input and output,
    // we must also disable IO on the output scope.

    UInt32 enableIO = 1;

    // Enable input on the AUHAL.
    result = AudioUnitSetProperty(audio_unit_,
        kAudioOutputUnitProperty_EnableIO,
        kAudioUnitScope_Input,
        1, // input element 1
        &enableIO, // enable
        sizeof(enableIO));
    if (result != noErr) {
        HandleError(result);
        return false;
    }

    // Disable output on the AUHAL.
    enableIO = 0;
    result = AudioUnitSetProperty(audio_unit_,
        kAudioOutputUnitProperty_EnableIO,
        kAudioUnitScope_Output,
        0, // output element 0
        &enableIO, // disable
        sizeof(enableIO));
    if (result != noErr) {
        HandleError(result);
        return false;
    }

    // Next, set the audio device to be the Audio Unit's current device.
    // Note that, devices can only be set to the AUHAL after enabling IO.
    result = AudioUnitSetProperty(audio_unit_,
        kAudioOutputUnitProperty_CurrentDevice,
        kAudioUnitScope_Global,
        0,
        &input_device_id_,
        sizeof(input_device_id_));
    if (result != noErr) {
        HandleError(result);
        return false;
    }

    // Register the input procedure for the AUHAL. This procedure will be called
    // when the AUHAL has received new data from the input device.
    AURenderCallbackStruct callback;
    callback.inputProc = &DataIsAvailable;
    callback.inputProcRefCon = this;
    result = AudioUnitSetProperty(
        audio_unit_, kAudioOutputUnitProperty_SetInputCallback,
        kAudioUnitScope_Global, 0, &callback, sizeof(callback));
    if (result != noErr) {
        HandleError(result);
        return false;
    }

    // Get the stream format for the selected input device and ensure that the
    // sample rate of the selected input device matches the desired (given at
    // construction) sample rate. We should not rely on sample rate conversion
    // in the AUHAL, only *simple* conversions, e.g., 32-bit float to 16-bit
    // signed integer format.
    AudioStreamBasicDescription input_device_format = { 0 };
    GetInputDeviceStreamFormat(audio_unit_, &input_device_format);
    if (input_device_format.mSampleRate != format_.mSampleRate) {
        LOG(ERROR)
            << "Input device's sample rate does not match the client's sample rate";
        result = kAudioUnitErr_FormatNotSupported;
        HandleError(result);
        return false;
    }

    // Modify the IO buffer size if not already set correctly for the selected
    // device. The status of other active audio input and output streams is
    // involved in the final setting.
    // TODO(henrika): we could make io_buffer_frame_size a member and add it to
    // the UMA stats tied to the Media.Audio.InputStartupSuccessMac record.
    size_t io_buffer_frame_size = 0;
    if (!manager_->MaybeChangeBufferSize(
            input_device_id_, audio_unit_, 1, number_of_frames_,
            &buffer_size_was_changed_, &io_buffer_frame_size)) {
        result = kAudioUnitErr_FormatNotSupported;
        HandleError(result);
        return false;
    }

    // Store current I/O buffer frame size for UMA stats stored in combination
    // with failing input callbacks.
    DCHECK(!io_buffer_frame_size_);
    io_buffer_frame_size_ = io_buffer_frame_size;

    // If |number_of_frames_| is out of range, the closest valid buffer size will
    // be set instead. Check the current setting and log a warning for a non
    // perfect match. Any such mismatch will be compensated for in
    // OnDataIsAvailable().
    UInt32 buffer_frame_size = 0;
    UInt32 property_size = sizeof(buffer_frame_size);
    result = AudioUnitGetProperty(
        audio_unit_, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global,
        0, &buffer_frame_size, &property_size);
    LOG_IF(WARNING, buffer_frame_size != number_of_frames_)
        << "AUHAL is using best match of IO buffer size: " << buffer_frame_size;

    // Channel mapping should be supported but add a warning just in case.
    // TODO(henrika): perhaps add to UMA stat to track if this can happen.
    DLOG_IF(WARNING,
        input_device_format.mChannelsPerFrame != format_.mChannelsPerFrame)
        << "AUHAL's audio converter must do channel conversion";

    // Set up the the desired (output) format.
    // For obtaining input from a device, the device format is always expressed
    // on the output scope of the AUHAL's Element 1.
    result = AudioUnitSetProperty(audio_unit_, kAudioUnitProperty_StreamFormat,
        kAudioUnitScope_Output, 1, &format_,
        sizeof(format_));
    if (result != noErr) {
        HandleError(result);
        return false;
    }

    // Finally, initialize the audio unit and ensure that it is ready to render.
    // Allocates memory according to the maximum number of audio frames
    // it can produce in response to a single render call.
    result = AudioUnitInitialize(audio_unit_);
    if (result != noErr) {
        HandleError(result);
        return false;
    }

    // The hardware latency is fixed and will not change during the call.
    hardware_latency_frames_ = GetHardwareLatency();

    // The master channel is 0, Left and right are channels 1 and 2.
    // And the master channel is not counted in |number_of_channels_in_frame_|.
    number_of_channels_in_frame_ = GetNumberOfChannelsFromStream();

    return true;
}

void AUAudioInputStream::Start(AudioInputCallback* callback)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DVLOG(1) << "Start";
    DCHECK(callback);
    DCHECK(!sink_);
    DLOG_IF(ERROR, !audio_unit_) << "Open() has not been called successfully";
    if (IsRunning())
        return;

    // Check if we should defer Start() for http://crbug.com/160920.
    if (manager_->ShouldDeferStreamStart()) {
        LOG(WARNING) << "Start of input audio is deferred";
        start_was_deferred_ = true;
        // Use a cancellable closure so that if Stop() is called before Start()
        // actually runs, we can cancel the pending start.
        deferred_start_cb_.Reset(base::Bind(
            &AUAudioInputStream::Start, base::Unretained(this), callback));
        manager_->GetTaskRunner()->PostDelayedTask(
            FROM_HERE, deferred_start_cb_.callback(),
            base::TimeDelta::FromSeconds(
                AudioManagerMac::kStartDelayInSecsForPowerEvents));
        return;
    }

    sink_ = callback;
    last_success_time_ = base::TimeTicks::Now();
    last_callback_time_ = base::TimeTicks::Now();
    audio_unit_render_has_worked_ = false;
    StartAgc();
    OSStatus result = AudioOutputUnitStart(audio_unit_);
    OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
        << "Failed to start acquiring data";
    if (result != noErr) {
        Stop();
        return;
    }
    DCHECK(IsRunning()) << "Audio unit started OK but is not yet running";

    // For UMA stat purposes, start a one-shot timer which detects when input
    // callbacks starts indicating if input audio recording starts as intended.
    // CheckInputStartupSuccess() will check if |input_callback_is_active_| is
    // true when the timer expires.
    input_callback_timer_.reset(new base::OneShotTimer());
    input_callback_timer_->Start(
        FROM_HERE,
        base::TimeDelta::FromSeconds(kInputCallbackStartTimeoutInSeconds), this,
        &AUAudioInputStream::CheckInputStartupSuccess);
    DCHECK(input_callback_timer_->IsRunning());

    // Also create and start a timer that provides periodic callbacks used to
    // monitor if the input stream is alive or not.
    check_alive_timer_.reset(new base::RepeatingTimer());
    check_alive_timer_->Start(
        FROM_HERE, base::TimeDelta::FromSeconds(kCheckInputIsAliveTimeInSeconds),
        this, &AUAudioInputStream::CheckIfInputStreamIsAlive);
    DCHECK(check_alive_timer_->IsRunning());
}

void AUAudioInputStream::Stop()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DVLOG(1) << "Stop";
    StopAgc();
    if (check_alive_timer_ != nullptr) {
        check_alive_timer_->Stop();
        check_alive_timer_.reset();
    }
    if (input_callback_timer_ != nullptr) {
        input_callback_timer_->Stop();
        input_callback_timer_.reset();
    }

    if (audio_unit_ != nullptr) {
        // Stop the I/O audio unit.
        OSStatus result = AudioOutputUnitStop(audio_unit_);
        DCHECK_EQ(result, noErr);
        // Add a DCHECK here just in case. AFAIK, the call to AudioOutputUnitStop()
        // seems to set this state synchronously, hence it should always report
        // false after a successful call.
        DCHECK(!IsRunning()) << "Audio unit is stopped but still running";

        // Reset the audio unit’s render state. This function clears memory.
        // It does not allocate or free memory resources.
        result = AudioUnitReset(audio_unit_, kAudioUnitScope_Global, 0);
        DCHECK_EQ(result, noErr);
        OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
            << "Failed to stop acquiring data";
    }

    SetInputCallbackIsActive(false);
    ReportAndResetStats();
    sink_ = nullptr;
    fifo_.Clear();
    io_buffer_frame_size_ = 0;
}

void AUAudioInputStream::Close()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DVLOG(1) << "Close";
    // It is valid to call Close() before calling open or Start().
    // It is also valid to call Close() after Start() has been called.
    Stop();
    // Uninitialize and dispose the audio unit.
    CloseAudioUnit();
    // Disable the listener for device property changes.
    DeRegisterDeviceChangeListener();
    // Add more UMA stats but only if AGC was activated, i.e. for e.g. WebRTC
    // audio input streams.
    if (GetAutomaticGainControl()) {
        // Check if any device property changes are added by filtering out a
        // selected set of the |device_property_changes_map_| map. Add UMA stats
        // if valuable data is found.
        AddDevicePropertyChangesToUMA(false);
        // Log whether call to Start() was deferred or not. To be compared with
        // Media.Audio.InputStartWasDeferredMac which logs the same value but only
        // when input audio fails to start.
        UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputStartWasDeferredAudioWorkedMac",
            start_was_deferred_);
        // Log if a change of I/O buffer size was required. To be compared with
        // Media.Audio.InputBufferSizeWasChangedMac which logs the same value but
        // only when input audio fails to start.
        UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputBufferSizeWasChangedAudioWorkedMac",
            buffer_size_was_changed_);
        // Logs the total number of times RestartAudio() has been called.
        DVLOG(1) << "Total number of restart attempts: "
                 << total_number_of_restart_attempts_;
        UMA_HISTOGRAM_COUNTS_1000("Media.Audio.InputRestartAttemptsMac",
            total_number_of_restart_attempts_);
        // TODO(henrika): possibly add more values here...
    }
    // Inform the audio manager that we have been closed. This will cause our
    // destruction.
    manager_->ReleaseInputStream(this);
}

double AUAudioInputStream::GetMaxVolume()
{
    // Verify that we have a valid device.
    if (input_device_id_ == kAudioObjectUnknown) {
        NOTREACHED() << "Device ID is unknown";
        return 0.0;
    }

    // Query if any of the master, left or right channels has volume control.
    for (int i = 0; i <= number_of_channels_in_frame_; ++i) {
        // If the volume is settable, the  valid volume range is [0.0, 1.0].
        if (IsVolumeSettableOnChannel(i))
            return 1.0;
    }

    // Volume control is not available for the audio stream.
    return 0.0;
}

void AUAudioInputStream::SetVolume(double volume)
{
    DVLOG(1) << "SetVolume(volume=" << volume << ")";
    DCHECK_GE(volume, 0.0);
    DCHECK_LE(volume, 1.0);

    // Verify that we have a valid device.
    if (input_device_id_ == kAudioObjectUnknown) {
        NOTREACHED() << "Device ID is unknown";
        return;
    }

    Float32 volume_float32 = static_cast<Float32>(volume);
    AudioObjectPropertyAddress property_address = {
        kAudioDevicePropertyVolumeScalar,
        kAudioDevicePropertyScopeInput,
        kAudioObjectPropertyElementMaster
    };

    // Try to set the volume for master volume channel.
    if (IsVolumeSettableOnChannel(kAudioObjectPropertyElementMaster)) {
        OSStatus result = AudioObjectSetPropertyData(
            input_device_id_, &property_address, 0, nullptr, sizeof(volume_float32),
            &volume_float32);
        if (result != noErr) {
            DLOG(WARNING) << "Failed to set volume to " << volume_float32;
        }
        return;
    }

    // There is no master volume control, try to set volume for each channel.
    int successful_channels = 0;
    for (int i = 1; i <= number_of_channels_in_frame_; ++i) {
        property_address.mElement = static_cast<UInt32>(i);
        if (IsVolumeSettableOnChannel(i)) {
            OSStatus result = AudioObjectSetPropertyData(
                input_device_id_, &property_address, 0, NULL, sizeof(volume_float32),
                &volume_float32);
            if (result == noErr)
                ++successful_channels;
        }
    }

    DLOG_IF(WARNING, successful_channels == 0)
        << "Failed to set volume to " << volume_float32;

    // Update the AGC volume level based on the last setting above. Note that,
    // the volume-level resolution is not infinite and it is therefore not
    // possible to assume that the volume provided as input parameter can be
    // used directly. Instead, a new query to the audio hardware is required.
    // This method does nothing if AGC is disabled.
    UpdateAgcVolume();
}

double AUAudioInputStream::GetVolume()
{
    // Verify that we have a valid device.
    if (input_device_id_ == kAudioObjectUnknown) {
        NOTREACHED() << "Device ID is unknown";
        return 0.0;
    }

    AudioObjectPropertyAddress property_address = {
        kAudioDevicePropertyVolumeScalar,
        kAudioDevicePropertyScopeInput,
        kAudioObjectPropertyElementMaster
    };

    if (AudioObjectHasProperty(input_device_id_, &property_address)) {
        // The device supports master volume control, get the volume from the
        // master channel.
        Float32 volume_float32 = 0.0;
        UInt32 size = sizeof(volume_float32);
        OSStatus result = AudioObjectGetPropertyData(input_device_id_, &property_address, 0,
            nullptr, &size, &volume_float32);
        if (result == noErr)
            return static_cast<double>(volume_float32);
    } else {
        // There is no master volume control, try to get the average volume of
        // all the channels.
        Float32 volume_float32 = 0.0;
        int successful_channels = 0;
        for (int i = 1; i <= number_of_channels_in_frame_; ++i) {
            property_address.mElement = static_cast<UInt32>(i);
            if (AudioObjectHasProperty(input_device_id_, &property_address)) {
                Float32 channel_volume = 0;
                UInt32 size = sizeof(channel_volume);
                OSStatus result = AudioObjectGetPropertyData(input_device_id_, &property_address, 0,
                    nullptr, &size, &channel_volume);
                if (result == noErr) {
                    volume_float32 += channel_volume;
                    ++successful_channels;
                }
            }
        }

        // Get the average volume of the channels.
        if (successful_channels != 0)
            return static_cast<double>(volume_float32 / successful_channels);
    }

    DLOG(WARNING) << "Failed to get volume";
    return 0.0;
}

bool AUAudioInputStream::IsMuted()
{
    // Verify that we have a valid device.
    DCHECK_NE(input_device_id_, kAudioObjectUnknown) << "Device ID is unknown";

    AudioObjectPropertyAddress property_address = {
        kAudioDevicePropertyMute,
        kAudioDevicePropertyScopeInput,
        kAudioObjectPropertyElementMaster
    };

    if (!AudioObjectHasProperty(input_device_id_, &property_address)) {
        DLOG(ERROR) << "Device does not support checking master mute state";
        return false;
    }

    UInt32 muted = 0;
    UInt32 size = sizeof(muted);
    OSStatus result = AudioObjectGetPropertyData(
        input_device_id_, &property_address, 0, nullptr, &size, &muted);
    DLOG_IF(WARNING, result != noErr) << "Failed to get mute state";
    return result == noErr && muted != 0;
}

// static
OSStatus AUAudioInputStream::DataIsAvailable(void* context,
    AudioUnitRenderActionFlags* flags,
    const AudioTimeStamp* time_stamp,
    UInt32 bus_number,
    UInt32 number_of_frames,
    AudioBufferList* io_data)
{
    DCHECK(context);
    // Recorded audio is always on the input bus (=1).
    DCHECK_EQ(bus_number, 1u);
    // No data buffer should be allocated at this stage.
    DCHECK(!io_data);
    AUAudioInputStream* self = reinterpret_cast<AUAudioInputStream*>(context);
    // Propagate render action flags, time stamp, bus number and number
    // of frames requested to the AudioUnitRender() call where the actual data
    // is received from the input device via the output scope of the audio unit.
    return self->OnDataIsAvailable(flags, time_stamp, bus_number,
        number_of_frames);
}

OSStatus AUAudioInputStream::OnDataIsAvailable(
    AudioUnitRenderActionFlags* flags,
    const AudioTimeStamp* time_stamp,
    UInt32 bus_number,
    UInt32 number_of_frames)
{
    TRACE_EVENT0("audio", "AUAudioInputStream::OnDataIsAvailable");
    // Update |last_callback_time_| on the main browser thread. Its value is used
    // by CheckIfInputStreamIsAlive() to detect if the stream is dead or alive.
    manager_->GetTaskRunner()->PostTask(
        FROM_HERE,
        base::Bind(&AUAudioInputStream::UpdateDataCallbackTimeOnMainThread,
            weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));

    // Indicate that input callbacks have started on the internal AUHAL IO
    // thread. The |input_callback_is_active_| member is read from the creating
    // thread when a timer fires once and set to false in Stop() on the same
    // thread. It means that this thread is the only writer of
    // |input_callback_is_active_| once the tread starts and it should therefore
    // be safe to modify.
    SetInputCallbackIsActive(true);

    // Update the |mDataByteSize| value in the audio_buffer_list() since
    // |number_of_frames| can be changed on the fly.
    // |mDataByteSize| needs to be exactly mapping to |number_of_frames|,
    // otherwise it will put CoreAudio into bad state and results in
    // AudioUnitRender() returning -50 for the new created stream.
    // We have also seen kAudioUnitErr_TooManyFramesToProcess (-10874) and
    // kAudioUnitErr_CannotDoInCurrentContext (-10863) as error codes.
    // See crbug/428706 for details.
    UInt32 new_size = number_of_frames * format_.mBytesPerFrame;
    AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers;
    bool new_buffer_size_detected = false;
    if (new_size != audio_buffer->mDataByteSize) {
        new_buffer_size_detected = true;
        DVLOG(1) << "New size of number_of_frames detected: " << number_of_frames;
        io_buffer_frame_size_ = static_cast<size_t>(number_of_frames);
        if (new_size > audio_buffer->mDataByteSize) {
            // This can happen if the device is unplugged during recording. We
            // allocate enough memory here to avoid depending on how CoreAudio
            // handles it.
            // See See http://www.crbug.com/434681 for one example when we can enter
            // this scope.
            audio_data_buffer_.reset(new uint8_t[new_size]);
            audio_buffer->mData = audio_data_buffer_.get();
        }

        // Update the |mDataByteSize| to match |number_of_frames|.
        audio_buffer->mDataByteSize = new_size;
    }

    // Obtain the recorded audio samples by initiating a rendering cycle.
    // Since it happens on the input bus, the |&audio_buffer_list_| parameter is
    // a reference to the preallocated audio buffer list that the audio unit
    // renders into.
    TRACE_EVENT_BEGIN0("audio", "AudioUnitRender");
    OSStatus result = AudioUnitRender(audio_unit_, flags, time_stamp, bus_number,
        number_of_frames, &audio_buffer_list_);
    TRACE_EVENT_END0("audio", "AudioUnitRender");
    if (result == noErr) {
        audio_unit_render_has_worked_ = true;
    }
    if (result) {
        TRACE_EVENT_INSTANT0("audio", "AudioUnitRender error",
            TRACE_EVENT_SCOPE_THREAD);
        // Only upload UMA histograms for the case when AGC is enabled. The reason
        // is that we want to compare these stats with others in this class and
        // they are only stored for "AGC streams", e.g. WebRTC audio streams.
        const bool add_uma_histogram = GetAutomaticGainControl();
        if (add_uma_histogram) {
            UMA_HISTOGRAM_SPARSE_SLOWLY("Media.AudioInputCbErrorMac", result);
        }
        OSSTATUS_LOG(ERROR, result) << "AudioUnitRender() failed ";
        if (result == kAudioUnitErr_TooManyFramesToProcess || result == kAudioUnitErr_CannotDoInCurrentContext) {
            DCHECK(!last_success_time_.is_null());
            // We delay stopping the stream for kAudioUnitErr_TooManyFramesToProcess
            // since it has been observed that some USB headsets can cause this error
            // but only for a few initial frames at startup and then then the stream
            // returns to a stable state again. See b/19524368 for details.
            // Instead, we measure time since last valid audio frame and call
            // HandleError() only if a too long error sequence is detected. We do
            // this to avoid ending up in a non recoverable bad core audio state.
            // Also including kAudioUnitErr_CannotDoInCurrentContext since long
            // sequences can be produced in combination with e.g. sample-rate changes
            // for input devices.
            base::TimeDelta time_since_last_success = base::TimeTicks::Now() - last_success_time_;
            if ((time_since_last_success > base::TimeDelta::FromSeconds(kMaxErrorTimeoutInSeconds))) {
                const char* err = (result == kAudioUnitErr_TooManyFramesToProcess)
                    ? "kAudioUnitErr_TooManyFramesToProcess"
                    : "kAudioUnitErr_CannotDoInCurrentContext";
                LOG(ERROR) << "Too long sequence of " << err << " errors!";
                HandleError(result);
            }

            // Add some extra UMA stat to track down if we see this particular error
            // in combination with a previous change of buffer size "on the fly".
            if (result == kAudioUnitErr_CannotDoInCurrentContext && add_uma_histogram) {
                UMA_HISTOGRAM_BOOLEAN("Media.Audio.RenderFailsWhenBufferSizeChangesMac",
                    new_buffer_size_detected);
                UMA_HISTOGRAM_BOOLEAN("Media.Audio.AudioUnitRenderHasWorkedMac",
                    audio_unit_render_has_worked_);
            }
        } else {
            // We have also seen kAudioUnitErr_NoConnection in some cases. Bailing
            // out for this error for now.
            HandleError(result);
        }
        return result;
    }
    // Update time of successful call to AudioUnitRender().
    last_success_time_ = base::TimeTicks::Now();

    // Deliver recorded data to the consumer as a callback.
    return Provide(number_of_frames, &audio_buffer_list_, time_stamp);
}

OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames,
    AudioBufferList* io_data,
    const AudioTimeStamp* time_stamp)
{
    TRACE_EVENT1("audio", "AUAudioInputStream::Provide", "number_of_frames",
        number_of_frames);
    UpdateCaptureTimestamp(time_stamp);
    last_number_of_frames_ = number_of_frames;

    // TODO(grunell): We'll only care about the first buffer size change, any
    // further changes will be ignored. This is in line with output side stats.
    // It would be nice to have all changes reflected in UMA stats.
    if (number_of_frames != number_of_frames_ && number_of_frames_provided_ == 0)
        number_of_frames_provided_ = number_of_frames;

    // Update the capture latency.
    double capture_latency_frames = GetCaptureLatency(time_stamp);

    // The AGC volume level is updated once every second on a separate thread.
    // Note that, |volume| is also updated each time SetVolume() is called
    // through IPC by the render-side AGC.
    double normalized_volume = 0.0;
    GetAgcVolume(&normalized_volume);

    AudioBuffer& buffer = io_data->mBuffers[0];
    uint8_t* audio_data = reinterpret_cast<uint8_t*>(buffer.mData);
    uint32_t capture_delay_bytes = static_cast<uint32_t>(
        (capture_latency_frames + 0.5) * format_.mBytesPerFrame);
    DCHECK(audio_data);
    if (!audio_data)
        return kAudioUnitErr_InvalidElement;

    // Dynamically increase capacity of the FIFO to handle larger buffers from
    // CoreAudio. This can happen in combination with Apple Thunderbolt Displays
    // when the Display Audio is used as capture source and the cable is first
    // remove and then inserted again.
    // See http://www.crbug.com/434681 for details.
    if (static_cast<int>(number_of_frames) > fifo_.GetUnfilledFrames()) {
        // Derive required increase in number of FIFO blocks. The increase is
        // typically one block.
        const int blocks = static_cast<int>((number_of_frames - fifo_.GetUnfilledFrames()) / number_of_frames_) + 1;
        DLOG(WARNING) << "Increasing FIFO capacity by " << blocks << " blocks";
        TRACE_EVENT_INSTANT1("audio", "Increasing FIFO capacity",
            TRACE_EVENT_SCOPE_THREAD, "increased by", blocks);
        fifo_.IncreaseCapacity(blocks);
    }

    // Copy captured (and interleaved) data into FIFO.
    fifo_.Push(audio_data, number_of_frames, format_.mBitsPerChannel / 8);

    // Consume and deliver the data when the FIFO has a block of available data.
    while (fifo_.available_blocks()) {
        const AudioBus* audio_bus = fifo_.Consume();
        DCHECK_EQ(audio_bus->frames(), static_cast<int>(number_of_frames_));

        // Compensate the audio delay caused by the FIFO.
        capture_delay_bytes += fifo_.GetAvailableFrames() * format_.mBytesPerFrame;
        sink_->OnData(this, audio_bus, capture_delay_bytes, normalized_volume);
    }

    return noErr;
}

void AUAudioInputStream::DevicePropertyChangedOnMainThread(
    const std::vector<UInt32>& properties)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DCHECK(device_listener_is_active_);
    // Use property as key to a map and increase its value. We are not
    // interested in all property changes but store all here anyhow.
    // Filtering will be done later in AddDevicePropertyChangesToUMA();
    for (auto property : properties) {
        DVLOG(2) << "=> " << FourCharFormatCodeToString(property);
        ++device_property_changes_map_[property];
    }
}

OSStatus AUAudioInputStream::OnDevicePropertyChanged(
    AudioObjectID object_id,
    UInt32 num_addresses,
    const AudioObjectPropertyAddress addresses[],
    void* context)
{
    AUAudioInputStream* self = static_cast<AUAudioInputStream*>(context);
    return self->DevicePropertyChanged(object_id, num_addresses, addresses);
}

OSStatus AUAudioInputStream::DevicePropertyChanged(
    AudioObjectID object_id,
    UInt32 num_addresses,
    const AudioObjectPropertyAddress addresses[])
{
    if (object_id != input_device_id_)
        return noErr;

    // Listeners will be called when possibly many properties have changed.
    // Consequently, the implementation of a listener must go through the array of
    // addresses to see what exactly has changed. Copy values into a local vector
    // and update the |device_property_changes_map_| on the main thread to avoid
    // potential race conditions.
    std::vector<UInt32> properties;
    properties.reserve(num_addresses);
    for (UInt32 i = 0; i < num_addresses; ++i) {
        properties.push_back(addresses[i].mSelector);
    }
    manager_->GetTaskRunner()->PostTask(
        FROM_HERE,
        base::Bind(&AUAudioInputStream::DevicePropertyChangedOnMainThread,
            weak_factory_.GetWeakPtr(), properties));
    return noErr;
}

void AUAudioInputStream::RegisterDeviceChangeListener()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DCHECK(!device_listener_is_active_);
    DVLOG(1) << "RegisterDeviceChangeListener";
    if (input_device_id_ == kAudioObjectUnknown)
        return;
    device_property_changes_map_.clear();
    OSStatus result = AudioObjectAddPropertyListener(
        input_device_id_, &kDeviceChangePropertyAddress,
        &AUAudioInputStream::OnDevicePropertyChanged, this);
    OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
        << "AudioObjectAddPropertyListener() failed!";
    device_listener_is_active_ = (result == noErr);
}

void AUAudioInputStream::DeRegisterDeviceChangeListener()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    if (!device_listener_is_active_)
        return;
    DVLOG(1) << "DeRegisterDeviceChangeListener";
    if (input_device_id_ == kAudioObjectUnknown)
        return;
    device_listener_is_active_ = false;
    OSStatus result = AudioObjectRemovePropertyListener(
        input_device_id_, &kDeviceChangePropertyAddress,
        &AUAudioInputStream::OnDevicePropertyChanged, this);
    OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
        << "AudioObjectRemovePropertyListener() failed!";
}

int AUAudioInputStream::HardwareSampleRate()
{
    // Determine the default input device's sample-rate.
    AudioDeviceID device_id = kAudioObjectUnknown;
    UInt32 info_size = sizeof(device_id);

    AudioObjectPropertyAddress default_input_device_address = {
        kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMaster
    };
    OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
        &default_input_device_address, 0,
        0, &info_size, &device_id);
    if (result != noErr)
        return 0.0;

    Float64 nominal_sample_rate;
    info_size = sizeof(nominal_sample_rate);

    AudioObjectPropertyAddress nominal_sample_rate_address = {
        kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMaster
    };
    result = AudioObjectGetPropertyData(device_id, &nominal_sample_rate_address,
        0, 0, &info_size, &nominal_sample_rate);
    if (result != noErr)
        return 0.0;

    return static_cast<int>(nominal_sample_rate);
}

double AUAudioInputStream::GetHardwareLatency()
{
    if (!audio_unit_ || input_device_id_ == kAudioObjectUnknown) {
        DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown";
        return 0.0;
    }

    // Get audio unit latency.
    Float64 audio_unit_latency_sec = 0.0;
    UInt32 size = sizeof(audio_unit_latency_sec);
    OSStatus result = AudioUnitGetProperty(
        audio_unit_, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0,
        &audio_unit_latency_sec, &size);
    OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
        << "Could not get audio unit latency";

    // Get input audio device latency.
    AudioObjectPropertyAddress property_address = {
        kAudioDevicePropertyLatency, kAudioDevicePropertyScopeInput,
        kAudioObjectPropertyElementMaster
    };
    UInt32 device_latency_frames = 0;
    size = sizeof(device_latency_frames);
    result = AudioObjectGetPropertyData(input_device_id_, &property_address, 0,
        nullptr, &size, &device_latency_frames);
    DLOG_IF(WARNING, result != noErr) << "Could not get audio device latency.";

    return static_cast<double>((audio_unit_latency_sec * format_.mSampleRate) + device_latency_frames);
}

double AUAudioInputStream::GetCaptureLatency(
    const AudioTimeStamp* input_time_stamp)
{
    // Get the delay between between the actual recording instant and the time
    // when the data packet is provided as a callback.
    UInt64 capture_time_ns = AudioConvertHostTimeToNanos(input_time_stamp->mHostTime);
    UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
    double delay_frames = static_cast<double>(1e-9 * (now_ns - capture_time_ns) * format_.mSampleRate);

    // Total latency is composed by the dynamic latency and the fixed
    // hardware latency.
    return (delay_frames + hardware_latency_frames_);
}

int AUAudioInputStream::GetNumberOfChannelsFromStream()
{
    // Get the stream format, to be able to read the number of channels.
    AudioObjectPropertyAddress property_address = {
        kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeInput,
        kAudioObjectPropertyElementMaster
    };
    AudioStreamBasicDescription stream_format;
    UInt32 size = sizeof(stream_format);
    OSStatus result = AudioObjectGetPropertyData(
        input_device_id_, &property_address, 0, nullptr, &size, &stream_format);
    if (result != noErr) {
        DLOG(WARNING) << "Could not get stream format";
        return 0;
    }

    return static_cast<int>(stream_format.mChannelsPerFrame);
}

bool AUAudioInputStream::IsRunning()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    if (!audio_unit_)
        return false;
    UInt32 is_running = 0;
    UInt32 size = sizeof(is_running);
    OSStatus error = AudioUnitGetProperty(audio_unit_, kAudioOutputUnitProperty_IsRunning,
        kAudioUnitScope_Global, 0, &is_running, &size);
    OSSTATUS_DLOG_IF(ERROR, error != noErr, error)
        << "AudioUnitGetProperty(kAudioOutputUnitProperty_IsRunning) failed";
    DVLOG(1) << "IsRunning: " << is_running;
    return (error == noErr && is_running);
}

void AUAudioInputStream::HandleError(OSStatus err)
{
    // Log the latest OSStatus error message and also change the sign of the
    // error if no callbacks are active. I.e., the sign of the error message
    // carries one extra level of information.
    UMA_HISTOGRAM_SPARSE_SLOWLY("Media.InputErrorMac",
        GetInputCallbackIsActive() ? err : (err * -1));
    NOTREACHED() << "error " << logging::DescriptionFromOSStatus(err) << " ("
                 << err << ")";
    if (sink_)
        sink_->OnError(this);
}

bool AUAudioInputStream::IsVolumeSettableOnChannel(int channel)
{
    Boolean is_settable = false;
    AudioObjectPropertyAddress property_address = {
        kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput,
        static_cast<UInt32>(channel)
    };
    OSStatus result = AudioObjectIsPropertySettable(
        input_device_id_, &property_address, &is_settable);
    return (result == noErr) ? is_settable : false;
}

void AUAudioInputStream::SetInputCallbackIsActive(bool enabled)
{
    base::subtle::Release_Store(&input_callback_is_active_, enabled);
}

bool AUAudioInputStream::GetInputCallbackIsActive()
{
    return (base::subtle::Acquire_Load(&input_callback_is_active_) != false);
}

void AUAudioInputStream::CheckInputStartupSuccess()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DCHECK(IsRunning());
    // Only add UMA stat related to failing input audio for streams where
    // the AGC has been enabled, e.g. WebRTC audio input streams.
    if (GetAutomaticGainControl()) {
        // Check if we have called Start() and input callbacks have actually
        // started in time as they should. If that is not the case, we have a
        // problem and the stream is considered dead.
        const bool input_callback_is_active = GetInputCallbackIsActive();
        UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputStartupSuccessMac",
            input_callback_is_active);
        DVLOG(1) << "input_callback_is_active: " << input_callback_is_active;
        if (!input_callback_is_active) {
            // Now when we know that startup has failed for some reason, add extra
            // UMA stats in an attempt to figure out the exact reason.
            AddHistogramsForFailedStartup();
        }
    }
}

void AUAudioInputStream::UpdateDataCallbackTimeOnMainThread(
    base::TimeTicks now_tick)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    last_callback_time_ = now_tick;
}

void AUAudioInputStream::CheckIfInputStreamIsAlive()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    // Avoid checking stream state if we are suspended.
    if (manager_->IsSuspending())
        return;
    // Restrict usage of the restart mechanism to "AGC streams" only.
    // TODO(henrika): if the restart scheme works well, we might include it
    // for all types of input streams.
    if (!GetAutomaticGainControl())
        return;

    // Clear this counter here when audio is active instead of in Start(),
    // otherwise it would be cleared at each restart attempt and that would break
    // the current design where only a certain number of restart attempts is
    // allowed.
    if (GetInputCallbackIsActive())
        number_of_restart_attempts_ = 0;

    // Measure time since last callback. |last_callback_time_| is set the first
    // time in Start() and then updated in each data callback, hence if
    // |time_since_last_callback| is large (>1) and growing for each check, the
    // callback sequence has stopped. A typical value under normal/working
    // conditions is a few milliseconds.
    base::TimeDelta time_since_last_callback = base::TimeTicks::Now() - last_callback_time_;
    DVLOG(2) << "time since last callback: "
             << time_since_last_callback.InSecondsF();

    // Increase a counter if it has been too long since the last data callback.
    // A non-zero value of this counter is a strong indication of a "dead" input
    // stream. Reset the same counter if the stream is alive.
    if (time_since_last_callback.InSecondsF() > 0.5 * kCheckInputIsAliveTimeInSeconds) {
        ++number_of_restart_indications_;
    } else {
        number_of_restart_indications_ = 0;
    }

    // Restart the audio stream if two conditions are met. First, the number of
    // restart indicators must be larger than a threshold, and secondly, only a
    // fixed number of restart attempts is allowed.
    // Note that, the threshold is different depending on if the stream is seen
    // as dead directly at the first call to Start() (i.e. it has never started)
    // or if the stream has started OK at least once but then stopped for some
    // reason (e.g by placing the device in sleep mode). One restart indication
    // is sufficient for the first case (threshold is zero), while a larger value
    // (threshold > 0) is required for the second case to avoid false alarms when
    // e.g. resuming from a suspended state.
    const size_t restart_threshold = GetInputCallbackIsActive() ? kNumberOfIndicationsToTriggerRestart : 0;
    if (number_of_restart_indications_ > restart_threshold && number_of_restart_attempts_ < kMaxNumberOfRestartAttempts) {
        SetInputCallbackIsActive(false);
        ++total_number_of_restart_attempts_;
        ++number_of_restart_attempts_;
        number_of_restart_indications_ = 0;
        RestartAudio();
    }
}

void AUAudioInputStream::CloseAudioUnit()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DVLOG(1) << "CloseAudioUnit";
    if (!audio_unit_)
        return;
    OSStatus result = AudioUnitUninitialize(audio_unit_);
    OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
        << "AudioUnitUninitialize() failed.";
    result = AudioComponentInstanceDispose(audio_unit_);
    OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
        << "AudioComponentInstanceDispose() failed.";
    audio_unit_ = 0;
}

void AUAudioInputStream::RestartAudio()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DVLOG(1) << "RestartAudio";
    LOG_IF(ERROR, IsRunning())
        << "Stream is reported dead but actually seems alive";
    if (!audio_unit_)
        return;

    // Store the existing  callback instance for upcoming call to Start().
    AudioInputCallback* sink = sink_;
    // Do a best-effort attempt to restart the presumably dead input audio stream.
    // TODO(henrika): initial tests shows that the scheme below works well but
    // there might be corner cases that I have missed.
    Stop();
    CloseAudioUnit();
    DeRegisterDeviceChangeListener();
    Open();
    Start(sink);
}

void AUAudioInputStream::AddHistogramsForFailedStartup()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputStartWasDeferredMac",
        start_was_deferred_);
    UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputBufferSizeWasChangedMac",
        buffer_size_was_changed_);
    UMA_HISTOGRAM_COUNTS_1000("Media.Audio.NumberOfOutputStreamsMac",
        manager_->output_streams());
    UMA_HISTOGRAM_COUNTS_1000("Media.Audio.NumberOfLowLatencyInputStreamsMac",
        manager_->low_latency_input_streams());
    UMA_HISTOGRAM_COUNTS_1000("Media.Audio.NumberOfBasicInputStreamsMac",
        manager_->basic_input_streams());
    // |number_of_frames_| is set at construction and corresponds to the
    // requested (by the client) number of audio frames per I/O buffer connected
    // to the selected input device. Ideally, this size will be the same as the
    // native I/O buffer size given by |io_buffer_frame_size_|.
    UMA_HISTOGRAM_SPARSE_SLOWLY("Media.Audio.RequestedInputBufferFrameSizeMac",
        number_of_frames_);
    DVLOG(1) << "number_of_frames_: " << number_of_frames_;
    // This value indicates the number of frames in the IO buffers connected
    // to the selected input device. It has been set by the audio manger in
    // Open() and can be the same as |number_of_frames_|, which is the desired
    // buffer size. These two values might differ if other streams are using the
    // same device and any of these streams have asked for a smaller buffer size.
    UMA_HISTOGRAM_SPARSE_SLOWLY("Media.Audio.ActualInputBufferFrameSizeMac",
        io_buffer_frame_size_);
    DVLOG(1) << "io_buffer_frame_size_: " << io_buffer_frame_size_;
    // TODO(henrika): this value will currently always report true. It should
    // be fixed when we understand the problem better.
    UMA_HISTOGRAM_BOOLEAN("Media.Audio.AutomaticGainControlMac",
        GetAutomaticGainControl());
    // Disable the listener for device property changes. Ensures that we don't
    // need a lock when reading the map.
    DeRegisterDeviceChangeListener();
    // Check if any device property changes are added by filtering out a selected
    // set of the |device_property_changes_map_| map. Add UMA stats if valuable
    // data is found.
    AddDevicePropertyChangesToUMA(true);
    // Add information about things like number of logical processors, number
    // of system resume events etc.
    AddSystemInfoToUMA(manager_->IsOnBatteryPower(),
        manager_->GetNumberOfResumeNotifications());
}

void AUAudioInputStream::AddDevicePropertyChangesToUMA(bool startup_failed)
{
    DVLOG(1) << "AddDevicePropertyChangesToUMA";
    DCHECK(!device_listener_is_active_);
    // Scan the map of all available property changes (notification types) and
    // filter out some that make sense to add to UMA stats.
    // TODO(henrika): figure out if the set of stats is sufficient or not.
    for (const auto& entry : device_property_changes_map_) {
        UInt32 device_property = entry.first;
        int change_count = entry.second;
        AudioDevicePropertyResult uma_result = PROPERTY_OTHER;
        switch (device_property) {
        case kAudioDevicePropertyDeviceHasChanged:
            uma_result = PROPERTY_DEVICE_HAS_CHANGED;
            DVLOG(1) << "kAudioDevicePropertyDeviceHasChanged";
            break;
        case kAudioDevicePropertyIOStoppedAbnormally:
            uma_result = PROPERTY_IO_STOPPED_ABNORMALLY;
            DVLOG(1) << "kAudioDevicePropertyIOStoppedAbnormally";
            break;
        case kAudioDevicePropertyHogMode:
            uma_result = PROPERTY_HOG_MODE;
            DVLOG(1) << "kAudioDevicePropertyHogMode";
            break;
        case kAudioDevicePropertyBufferFrameSize:
            uma_result = PROPERTY_BUFFER_FRAME_SIZE;
            DVLOG(1) << "kAudioDevicePropertyBufferFrameSize";
            break;
        case kAudioDevicePropertyBufferFrameSizeRange:
            uma_result = PROPERTY_BUFFER_FRAME_SIZE_RANGE;
            DVLOG(1) << "kAudioDevicePropertyBufferFrameSizeRange";
            break;
        case kAudioDevicePropertyStreamConfiguration:
            uma_result = PROPERTY_STREAM_CONFIGURATION;
            DVLOG(1) << "kAudioDevicePropertyStreamConfiguration";
            break;
        case kAudioDevicePropertyActualSampleRate:
            uma_result = PROPERTY_ACTUAL_SAMPLE_RATE;
            DVLOG(1) << "kAudioDevicePropertyActualSampleRate";
            break;
        case kAudioDevicePropertyNominalSampleRate:
            uma_result = PROPERTY_NOMINAL_SAMPLE_RATE;
            DVLOG(1) << "kAudioDevicePropertyNominalSampleRate";
            break;
        case kAudioDevicePropertyDeviceIsRunningSomewhere:
            uma_result = PROPERTY_DEVICE_IS_RUNNING_SOMEWHERE;
            DVLOG(1) << "kAudioDevicePropertyDeviceIsRunningSomewhere";
            break;
        case kAudioDevicePropertyDeviceIsRunning:
            uma_result = PROPERTY_DEVICE_IS_RUNNING;
            DVLOG(1) << "kAudioDevicePropertyDeviceIsRunning";
            break;
        case kAudioDevicePropertyDeviceIsAlive:
            uma_result = PROPERTY_DEVICE_IS_ALIVE;
            DVLOG(1) << "kAudioDevicePropertyDeviceIsAlive";
            break;
        case kAudioStreamPropertyPhysicalFormat:
            uma_result = PROPERTY_STREAM_PHYSICAL_FORMAT;
            DVLOG(1) << "kAudioStreamPropertyPhysicalFormat";
            break;
        case kAudioDevicePropertyJackIsConnected:
            uma_result = PROPERTY_JACK_IS_CONNECTED;
            DVLOG(1) << "kAudioDevicePropertyJackIsConnected";
            break;
        case kAudioDeviceProcessorOverload:
            uma_result = PROPERTY_PROCESSOR_OVERLOAD;
            DVLOG(1) << "kAudioDeviceProcessorOverload";
            break;
        case kAudioDevicePropertyDataSources:
            uma_result = PROPERTY_DATA_SOURCES;
            DVLOG(1) << "kAudioDevicePropertyDataSources";
            break;
        case kAudioDevicePropertyDataSource:
            uma_result = PROPERTY_DATA_SOURCE;
            DVLOG(1) << "kAudioDevicePropertyDataSource";
            break;
        case kAudioDevicePropertyVolumeDecibels:
            uma_result = PROPERTY_VOLUME_DECIBELS;
            DVLOG(1) << "kAudioDevicePropertyVolumeDecibels";
            break;
        case kAudioDevicePropertyVolumeScalar:
            uma_result = PROPERTY_VOLUME_SCALAR;
            DVLOG(1) << "kAudioDevicePropertyVolumeScalar";
            break;
        case kAudioDevicePropertyMute:
            uma_result = PROPERTY_MUTE;
            DVLOG(1) << "kAudioDevicePropertyMute";
            break;
        case kAudioDevicePropertyPlugIn:
            uma_result = PROPERTY_PLUGIN;
            DVLOG(1) << "kAudioDevicePropertyPlugIn";
            break;
        case kAudioDevicePropertyUsesVariableBufferFrameSizes:
            uma_result = PROPERTY_USES_VARIABLE_BUFFER_FRAME_SIZES;
            DVLOG(1) << "kAudioDevicePropertyUsesVariableBufferFrameSizes";
            break;
        case kAudioDevicePropertyIOCycleUsage:
            uma_result = PROPERTY_IO_CYCLE_USAGE;
            DVLOG(1) << "kAudioDevicePropertyIOCycleUsage";
            break;
        case kAudioDevicePropertyIOProcStreamUsage:
            uma_result = PROPERTY_IO_PROC_STREAM_USAGE;
            DVLOG(1) << "kAudioDevicePropertyIOProcStreamUsage";
            break;
        case kAudioDevicePropertyConfigurationApplication:
            uma_result = PROPERTY_CONFIGURATION_APPLICATION;
            DVLOG(1) << "kAudioDevicePropertyConfigurationApplication";
            break;
        case kAudioDevicePropertyDeviceUID:
            uma_result = PROPERTY_DEVICE_UID;
            DVLOG(1) << "kAudioDevicePropertyDeviceUID";
            break;
        case kAudioDevicePropertyModelUID:
            uma_result = PROPERTY_MODE_UID;
            DVLOG(1) << "kAudioDevicePropertyModelUID";
            break;
        case kAudioDevicePropertyTransportType:
            uma_result = PROPERTY_TRANSPORT_TYPE;
            DVLOG(1) << "kAudioDevicePropertyTransportType";
            break;
        case kAudioDevicePropertyRelatedDevices:
            uma_result = PROPERTY_RELATED_DEVICES;
            DVLOG(1) << "kAudioDevicePropertyRelatedDevices";
            break;
        case kAudioDevicePropertyClockDomain:
            uma_result = PROPERTY_CLOCK_DOMAIN;
            DVLOG(1) << "kAudioDevicePropertyClockDomain";
            break;
        case kAudioDevicePropertyDeviceCanBeDefaultDevice:
            uma_result = PROPERTY_DEVICE_CAN_BE_DEFAULT_DEVICE;
            DVLOG(1) << "kAudioDevicePropertyDeviceCanBeDefaultDevice";
            break;
        case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice:
            uma_result = PROPERTY_DEVICE_CAN_BE_DEFAULT_SYSTEM_DEVICE;
            DVLOG(1) << "kAudioDevicePropertyDeviceCanBeDefaultSystemDevice";
            break;
        case kAudioDevicePropertyLatency:
            uma_result = PROPERTY_LATENCY;
            DVLOG(1) << "kAudioDevicePropertyLatency";
            break;
        case kAudioDevicePropertyStreams:
            uma_result = PROPERTY_STREAMS;
            DVLOG(1) << "kAudioDevicePropertyStreams";
            break;
        case kAudioObjectPropertyControlList:
            uma_result = PROPERTY_CONTROL_LIST;
            DVLOG(1) << "kAudioObjectPropertyControlList";
            break;
        case kAudioDevicePropertySafetyOffset:
            uma_result = PROPERTY_SAFETY_OFFSET;
            DVLOG(1) << "kAudioDevicePropertySafetyOffset";
            break;
        case kAudioDevicePropertyAvailableNominalSampleRates:
            uma_result = PROPERTY_AVAILABLE_NOMINAL_SAMPLE_RATES;
            DVLOG(1) << "kAudioDevicePropertyAvailableNominalSampleRates";
            break;
        case kAudioDevicePropertyIcon:
            uma_result = PROPERTY_ICON;
            DVLOG(1) << "kAudioDevicePropertyIcon";
            break;
        case kAudioDevicePropertyIsHidden:
            uma_result = PROPERTY_IS_HIDDEN;
            DVLOG(1) << "kAudioDevicePropertyIsHidden";
            break;
        case kAudioDevicePropertyPreferredChannelsForStereo:
            uma_result = PROPERTY_PREFERRED_CHANNELS_FOR_STEREO;
            DVLOG(1) << "kAudioDevicePropertyPreferredChannelsForStereo";
            break;
        case kAudioDevicePropertyPreferredChannelLayout:
            uma_result = PROPERTY_PREFERRED_CHANNEL_LAYOUT;
            DVLOG(1) << "kAudioDevicePropertyPreferredChannelLayout";
            break;
        case kAudioDevicePropertyVolumeRangeDecibels:
            uma_result = PROPERTY_VOLUME_RANGE_DECIBELS;
            DVLOG(1) << "kAudioDevicePropertyVolumeRangeDecibels";
            break;
        case kAudioDevicePropertyVolumeScalarToDecibels:
            uma_result = PROPERTY_VOLUME_SCALAR_TO_DECIBELS;
            DVLOG(1) << "kAudioDevicePropertyVolumeScalarToDecibels";
            break;
        case kAudioDevicePropertyVolumeDecibelsToScalar:
            uma_result = PROPERTY_VOLUME_DECIBEL_TO_SCALAR;
            DVLOG(1) << "kAudioDevicePropertyVolumeDecibelsToScalar";
            break;
        case kAudioDevicePropertyStereoPan:
            uma_result = PROPERTY_STEREO_PAN;
            DVLOG(1) << "kAudioDevicePropertyStereoPan";
            break;
        case kAudioDevicePropertyStereoPanChannels:
            uma_result = PROPERTY_STEREO_PAN_CHANNELS;
            DVLOG(1) << "kAudioDevicePropertyStereoPanChannels";
            break;
        case kAudioDevicePropertySolo:
            uma_result = PROPERTY_SOLO;
            DVLOG(1) << "kAudioDevicePropertySolo";
            break;
        case kAudioDevicePropertyPhantomPower:
            uma_result = PROPERTY_PHANTOM_POWER;
            DVLOG(1) << "kAudioDevicePropertyPhantomPower";
            break;
        case kAudioDevicePropertyPhaseInvert:
            uma_result = PROPERTY_PHASE_INVERT;
            DVLOG(1) << "kAudioDevicePropertyPhaseInvert";
            break;
        case kAudioDevicePropertyClipLight:
            uma_result = PROPERTY_CLIP_LIGHT;
            DVLOG(1) << "kAudioDevicePropertyClipLight";
            break;
        case kAudioDevicePropertyTalkback:
            uma_result = PROPERTY_TALKBACK;
            DVLOG(1) << "kAudioDevicePropertyTalkback";
            break;
        case kAudioDevicePropertyListenback:
            uma_result = PROPERTY_LISTENBACK;
            DVLOG(1) << "kAudioDevicePropertyListenback";
            break;
        case kAudioDevicePropertyClockSource:
            uma_result = PROPERTY_CLOCK_SOURCE;
            DVLOG(1) << "kAudioDevicePropertyClockSource";
            break;
        case kAudioDevicePropertyClockSources:
            uma_result = PROPERTY_CLOCK_SOURCES;
            DVLOG(1) << "kAudioDevicePropertyClockSources";
            break;
        case kAudioDevicePropertySubMute:
            uma_result = PROPERTY_SUB_MUTE;
            DVLOG(1) << "kAudioDevicePropertySubMute";
            break;
        default:
            uma_result = PROPERTY_OTHER;
            DVLOG(1) << "Property change is ignored";
            break;
        }
        DVLOG(1) << "property: " << device_property << " ("
                 << FourCharFormatCodeToString(device_property) << ")"
                 << " changed: " << change_count;
        LogDevicePropertyChange(startup_failed, uma_result);
    }
    device_property_changes_map_.clear();
}

void AUAudioInputStream::UpdateCaptureTimestamp(
    const AudioTimeStamp* timestamp)
{
    if ((timestamp->mFlags & kAudioTimeStampSampleTimeValid) == 0)
        return;

    if (last_sample_time_) {
        DCHECK_NE(0U, last_number_of_frames_);
        UInt32 diff = static_cast<UInt32>(timestamp->mSampleTime - last_sample_time_);
        if (diff != last_number_of_frames_) {
            DCHECK_GT(diff, last_number_of_frames_);
            // We were given samples post what we expected. Update the glitch count
            // etc. and keep a record of the largest glitch.
            auto lost_frames = diff - last_number_of_frames_;
            total_lost_frames_ += lost_frames;
            if (lost_frames > largest_glitch_frames_)
                largest_glitch_frames_ = lost_frames;
            ++glitches_detected_;
        }
    }

    // Store the last sample time for use next time we get called back.
    last_sample_time_ = timestamp->mSampleTime;
}

void AUAudioInputStream::ReportAndResetStats()
{
    if (last_sample_time_ == 0)
        return; // No stats gathered to report.

    // A value of 0 indicates that we got the buffer size we asked for.
    UMA_HISTOGRAM_COUNTS_10000("Media.Audio.Capture.FramesProvided",
        number_of_frames_provided_);
    // Even if there aren't any glitches, we want to record it to get a feel for
    // how often we get no glitches vs the alternative.
    UMA_HISTOGRAM_COUNTS("Media.Audio.Capture.Glitches", glitches_detected_);

    auto lost_frames_ms = (total_lost_frames_ * 1000) / format_.mSampleRate;
    std::string log_message = base::StringPrintf(
        "AU in: Total glitches=%d. Total frames lost=%d (%.0lf ms).",
        glitches_detected_, total_lost_frames_, lost_frames_ms);
    log_callback_.Run(log_message);

    if (glitches_detected_ != 0) {
        UMA_HISTOGRAM_LONG_TIMES("Media.Audio.Capture.LostFramesInMs",
            base::TimeDelta::FromMilliseconds(lost_frames_ms));
        auto largest_glitch_ms = (largest_glitch_frames_ * 1000) / format_.mSampleRate;
        UMA_HISTOGRAM_CUSTOM_TIMES(
            "Media.Audio.Capture.LargestGlitchMs",
            base::TimeDelta::FromMilliseconds(largest_glitch_ms),
            base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
            50);
        DLOG(WARNING) << log_message;
    }

    number_of_frames_provided_ = 0;
    glitches_detected_ = 0;
    last_sample_time_ = 0;
    last_number_of_frames_ = 0;
    total_lost_frames_ = 0;
    largest_glitch_frames_ = 0;
}

} // namespace media
